package org.dicr.jmx.adapter.hessian;

import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.util.*;
import java.util.logging.*;

import javax.management.*;

import com.caucho.hessian.io.*;
import com.caucho.hessian.server.*;

/**
 * @author not attributable
 * @version 1.0
 */
public class HessianAdapter implements HessianAdapterMBean, MBeanRegistration {

	protected static final Logger log = Logger.getLogger(HessianAdapter.class.getPackage().getName());

	protected int port = 8780;

	protected MBeanServer server = null;

	protected ObjectName objectName = null;

	protected Class<?> apiClass = null;

	protected HessianSkeleton hessian = null;

	protected transient SocketListener socketListener = null;

	public HessianAdapter() {
		super();
	}

	@Override
	public synchronized void setPort(int the_port) {
		if (the_port <= 0) {
			throw new IllegalArgumentException("port: " + the_port);
		}
		this.port = the_port;
	}

	public synchronized void setObjectName(ObjectName name) {
		if (name == null) {
			throw new IllegalArgumentException("null name");
		}
		this.objectName = name;
	}

	public synchronized void setObjectName(String name) throws MalformedObjectNameException {
		if (name == null) {
			throw new IllegalArgumentException("null name");
		}
		setObjectName(ObjectName.getInstance(name));
	}

	public synchronized void setApiClass(Class<?> api) {
		if (api == null) {
			throw new IllegalArgumentException("null api");
		}
		this.apiClass = api;
	}

	public synchronized void setApiClass(String className) throws ClassNotFoundException {
		if (className == null) {
			throw new IllegalArgumentException("null className");
		}
		setApiClass(Class.forName(className));
	}

	@Override
	public synchronized void start() {
		if (this.socketListener == null || !this.socketListener.isAlive()) {
			this.socketListener = new SocketListener();
			this.socketListener.start();
		}
	}

	@Override
	public synchronized void stop() {
		if (this.socketListener != null) {
			if (this.socketListener.isAlive()) this.socketListener.interrupt();
			else this.socketListener = null;
		}
	}

	@Override
	public ObjectName preRegister(MBeanServer the_server, ObjectName name) throws Exception {
		this.server = the_server;
		return name != null ? name : ObjectName.getInstance("org.dicr.service", "name", "HessianAdapter");
	}

	@Override
	public void postRegister(Boolean registrationDone) {
		if (registrationDone.booleanValue()) start();
	}

	@Override
	public void preDeregister() throws Exception {
		stop();
	}

	@Override
	public void postDeregister() {
		// NOP
	}

	class SocketListener extends Thread {

		@Override
		public void run() {
			log.info("starting Hessian JMX adapter at port " + HessianAdapter.this.port);
			try (Selector selector = Selector.open(); ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
				serverChannel.configureBlocking(false);
				serverChannel.socket().bind(new InetSocketAddress(HessianAdapter.this.port));
				serverChannel.register(selector, SelectionKey.OP_ACCEPT);
				while (!isInterrupted()) { // && selector.select() > 0) {
					selector.select();
					Set<SelectionKey> readyKeys = selector.selectedKeys();
					Iterator<SelectionKey> it = readyKeys.iterator();
					while (it.hasNext()) {
						SelectionKey key = it.next();
						ServerSocketChannel nextReady = (ServerSocketChannel) key.channel();
						Socket s = nextReady.accept().socket();
						new SocketHandler(s).start();
					}
				}
			} catch (ClosedByInterruptException ex) {
				// NOP
			} catch (IOException ex) {
				log.log(Level.WARNING, "error handling socket", ex);
			}
			log.info("stopping Hessian JMX adapter at port " + HessianAdapter.this.port);
			HessianAdapter.this.socketListener = null;
		}
	}

	class SocketHandler extends Thread {

		protected Socket sock = null;

		public SocketHandler(Socket the_sock) {
			if (the_sock == null) {
				throw new IllegalArgumentException("null sock");
			}
			this.sock = the_sock;
		}

		protected void printReply(OutputStream ou, byte[] reply) {
			if (ou != null) {
				try {
					PrintStream w = new PrintStream(ou);
					w.println("HTTP/1.0 200 OK");
					w.println("Content-Type: application/binary");
					w.println("Content-length: " + reply.length);
					w.println("Connection: close");
					w.println();
					w.write(reply);
					w.println();
					w.flush();
					w.close();
				} catch (Exception ex) {
					// NOP
				}
			}
		}

		protected void printError(OutputStream ou, Throwable th) {
			if (th == null) {
				throw new IllegalArgumentException("null th");
			}
			if (ou != null) {
				try {
					PrintStream w = new PrintStream(ou);
					w.println("HTTP/1.0 500 " + th);
					w.println("Content-length: 1");
					w.println();
					w.write(0);
					w.flush();
					w.close();
				} catch (Exception ex) {
					// NOP
				}
			}
		}

		@Override
		public void run() {
			InputStream in = null;
			OutputStream ou = null;
			try {
				in = this.sock.getInputStream();
				ou = this.sock.getOutputStream();

				// skip headers
				StringBuilder sb = new StringBuilder();
				while (true) {
					int b = in.read();
					if (b == '\n') {
						if (sb.toString().trim().length() < 1) break;
						sb = new StringBuilder();
					} else {
						sb.append((char) b);
					}
				}

				// first-init hessian
				if (HessianAdapter.this.hessian == null) {
					Object target = MBeanServerInvocationHandler
							.newProxyInstance(HessianAdapter.this.server,
									ObjectName.getInstance(HessianAdapter.this.objectName),
									HessianAdapter.this.apiClass, false);
					HessianAdapter.this.hessian = new HessianSkeleton(target, HessianAdapter.this.apiClass);
				}

				ByteArrayOutputStream bou = new ByteArrayOutputStream();
				HessianAdapter.this.hessian.invoke(new HessianInput(in), new HessianOutput(bou));
				bou.flush();
				bou.close();
				printReply(ou, bou.toByteArray());
			} catch (Throwable ex) {
				printError(ou, ex);
				log.log(Level.WARNING, "нЬХАЙЮ БШГНБЮ ТСМЙЖХХ", ex);
			} finally {
				if (ou != null) {
					try {
						ou.flush();
						ou.close();
					} catch (IOException ex) {
						// NOP
					}
				}
				if (in != null) {
					try {
						in.close();
					} catch (IOException ex) {
						// NOP
					}
				}
			}
		}
	}
}
